import random
import logging

from django_redis import get_redis_connection
from decimal import Decimal
from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from goods.models import SKU
from . import constants
from .models import OrderInfo, OrderGoods

logger = logging.getLogger('django')


class CartSKUSerializer(serializers.ModelSerializer):
    """ 购物车商品数据序列化器"""
    count = serializers.IntegerField(label='数量')

    class Meta:
        model = SKU
        fields = ['id', 'name', 'default_image_url', 'price', 'count']


class OrderSettlementSerializer(serializers.Serializer):
    """订单结算数据序列化器"""
    freight = serializers.DecimalField(label='运费', max_digits=constants.MEMORY, decimal_places=2)
    skus = CartSKUSerializer(many=True)


class SaveOrderSerializer(serializers.ModelSerializer):
    """下单数据序列化器"""

    class Meta:
        model = OrderInfo
        fields = ['order_id', 'address', 'pay_method']
        read_only_fields = ['order_id']
        extra_kwargs = {
            'address': {
                'write_only': True,
                'required': True
            },
            'pay_method': {
                'write_only': True,
                'required': True
            }

        }

    def create(self, validated_data):
        """保存订单"""
        # 获取当前下单用户
        user = self.context['request'].user
        # 生成订单编号
        # order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) + random.randint(0, 10000)
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ("%09d" % user.id)
        # 保存订单基本信息数据 OrderInfo
        address = validated_data['address']
        pay_method = validated_data['pay_method']
        # 生成订单
        # 使用django创建事务保存点
        with transaction.atomic():
            save_id = transaction.savepoint()

            try:
                # 创建订单信息
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal(0),
                    freight=Decimal(10),
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM[
                        'CASH']
                    else OrderInfo.ORDER_STATUS_ENUM['UNPAID']
                )
                # 获取购物车信息(从redis中获取购物车结算商品数据)
                redis_conn = get_redis_connection('cart')
                redis_cart = redis_conn.hgetall('cart_%s' % user.id)
                cart_selected = redis_conn.smembers('cart_selected_%s' % user.id)
                # 获取购物车所有勾选状态为True的商品信息
                # 将bytes类型转换为int类型
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])
                """
                cart = {
                  sku_id1: count,
                  sku_id2: count,
                  ...
                }
                """
                # 遍历结算商品：
                for sku_id in cart:
                    while (True):
                        # 获取当前sku_id对应的商品信息
                        sku = SKU.objects.get(pk=sku_id)
                        # 本次购买的商品的sku的数量
                        sku_count = cart[sku.id]
                        # 判断商品库存是否充足
                        origin_stock = sku.stock  # 原始库存【这里使用乐观锁需要先保存原始的数据】
                        origin_sales = sku.sales  # 原始销量
                        if sku_count > origin_stock:
                            # 库存不足，把sql操作会滚到with语句的开端
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('商品库存不足')
                        # 用于演示并发下单,测试用
                        # import time
                        # time.sleep(5)

                        # 5.2 减少商品库存，增加商品销量[sku单品销量、SPU总销量]
                        # sku.stock = origin_stock - sku_count # 新库存
                        # sku.sales = origin_sales + sku_count # 新销量
                        # sku.save()

                        # 调整代码，使用乐观锁
                        new_stock = origin_stock - sku_count  # 新库存
                        new_sales = origin_sales + sku_count  # 新销量
                        """
                        使用乐观锁的步骤
                        1，先记录在查询数据时，保存原始数据值作为更新的条件
                        ２，在更新的时候，把原始数据值作为更新的条件
                        """
                        ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                        # 在乐观锁中并没有保存成功，那么就让用户重写判断商品库存
                        if ret == 0:
                            # 跳过本次循环
                            continue

                        # 如果ret的值不是0，则表示当前sku上的库存是充足的，也就是更新成功则执行后续代码
                        break
                    sku.goods.sales += sku_count
                    sku.goods.save()

                    # 累计订单基本信息的数据
                    order.total_count += sku_count  # 累计本次订单的商品数量
                    order.total_amount += (sku.price * sku_count)  # 累计本次订单的总金额
                    # 保存当前订单的商品数据
                    OrderGoods.objects.create(
                        order=order,
                        sku=sku,
                        count=sku_count,
                        price=sku.price,
                    )
                order.total_amount += order.freight
                order.save()
            except serializers.ValidationError:
                # 这里表示是序列化器，而校验错误抛出的异常是要给视图处理，返回给前端
                # 所以这里，我们这里不做任何处理，直接使用 raise　可以把当前错误往视图抛出
                # raise　可以把当前错误往视图抛出
                raise
            except Exception as e:
                logger.error(e)
                # 如果出现别的异常，也要回滚视图，取消with语句中sql语句
                transaction.savepoint_rollback(save_id)
                raise
                # 提交事务！！
            transaction.savepoint_commit(save_id)
            # 减少商品库存，增加商品销量
            # 保存订单商品数据
            # 在redis购物车中删除已计算商品数据
            # 在redis购物车中删除已计算商品数据
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, *cart_selected)
            pl.srem('cart_selected_%s' % user.id, *cart_selected)
            pl.execute()

            return order
